package cn.amossun.starter.event.handler;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.amossun.starter.event.annoation.*;
import cn.amossun.starter.event.annoation.EventListener;
import cn.amossun.starter.event.exception.EventConfigException;
import cn.amossun.starter.event.property.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

@Data
@Slf4j
@NoArgsConstructor
@AllArgsConstructor
public class EventContextProcessor implements BeanPostProcessor, EventContextHolder {

    private EventContext eventContext;

    private QueueContextHolder queueContextHolder;

    private BindingContextHolder bindingContextHolder;

    private List<EventContextHolder> eventContextHolderList;

    private ListenerInvokeHolder<ListenerContext> listenerInvokeHolder;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass());
        if (methods != null) {
            for (Method method : methods) {
                this.processListener(bean, beanName, method);

                this.processPublisher(bean, beanName, method);

                this.processMulticaster(bean, beanName, method);
            }
        }
        return bean;
    }

    @Override
    public void processMulticaster(Object bean, String beanName, Method method) {
        EventMulticaster eventMulticaster = AnnotationUtils.findAnnotation(method, EventMulticaster.class);
        if (eventMulticaster != null) {

            validation(eventMulticaster);

            eventContextHolderList.stream().forEach(eventContextHolder -> {
                eventContextHolder.processMulticaster(bean, beanName, method);
            });
        }
    }

    @Override
    public void processPublisher(Object bean, String beanName, Method method) {
        EventPublisher eventPublisher = AnnotationUtils.findAnnotation(method, EventPublisher.class);
        if (eventPublisher != null) {

            validation(eventPublisher);

            eventContextHolderList.stream().forEach(eventContextHolder -> {
                eventContextHolder.processPublisher(bean, beanName, method);
            });
        }
    }

    @Override
    public void processListener(Object bean, String beanName, Method method) {
        EventListener eventListener = AnnotationUtils.findAnnotation(method, EventListener.class);

        if (eventListener != null) {

            validation(eventListener);

            eventContextHolderList.stream().forEach(eventContextHolder -> {
                eventContextHolder.processListener(bean, beanName, method);
            });

            List<String> queueNames = queueContextHolder.getQueueNames(bean, beanName, method, eventListener);
            ListenerContext listenerContext = listenerInvokeHolder.processListenerInvoke(bean, beanName, method);
            ListenerInvokeHandler listenerInvokeHandler = new ListenerInvokeHandler(queueNames, bean, method, listenerContext);

            List<String> listenerKeys = bindingContextHolder.getListenerKeys(eventListener);
            addListenerInvokeBean(listenerKeys, listenerInvokeHandler);
        }
    }

    protected void addListenerInvokeBean(List<String> listenerKeys, ListenerInvokeHandler listenerInvokeHandler) {
        listenerKeys.stream().forEach(listenerKey->{
            List<ListenerInvokeHandler> listenerInvokeHandlers = getListenerInvokeHandlers(listenerKey);
            listenerInvokeHandlers.add(listenerInvokeHandler);
            eventContext.getInvokeHandlerMap().put(listenerKey, listenerInvokeHandlers);
        });
    }

    protected List getListenerInvokeHandlers(String listenerKey) {
        List<ListenerInvokeHandler> listenerInvokeHandlers = eventContext.getInvokeHandlerMap().get(listenerKey);
        if (null == listenerInvokeHandlers) {
            listenerInvokeHandlers = new ArrayList<>(5);
        }
        return listenerInvokeHandlers;
    }

    protected void validation(EventMulticaster eventMulticaster) {
        if (eventMulticaster == null) {
            throw new NullPointerException("@EventMulticaster can not be empty");
        }

        if(CollectionUtil.isEmpty(Arrays.asList(eventMulticaster.bindings()))
                || CollectionUtil.isEmpty(Arrays.stream(eventMulticaster.bindings()).map(EventBinding::key).collect(Collectors.toList()))) {
            throw new EventConfigException("@EventMulticaster keys or bindings can not be empty");
        }

        if(eventMulticaster.bindings().length > 1) {
            List<EventBinding> eventBindingList = Arrays.asList(eventMulticaster.bindings());
            eventBindingList.stream().forEach(source->{
                if(StrUtil.isEmpty(source.key())){
                    throw new EventConfigException("@EventMulticaster bindings key can not be empty");
                }
            });
        }

    }

    protected void validation(EventPublisher eventPublisher) {
        if (eventPublisher == null) {
            throw new NullPointerException("@EventPublisher can not be empty");
        }

        if(!eventPublisher.defaultTopicExchange() && !eventPublisher.defaultHeadersExchange() && eventPublisher.exchange() == null) {
            throw new EventConfigException("@EventPublisher exchange can not be empty");
        }

        if(!eventPublisher.defaultTopicExchange() && !eventPublisher.defaultHeadersExchange() && StrUtil.isEmpty(eventPublisher.exchange().name())) {
            throw new EventConfigException("@EventPublisher exchange name can not be empty");
        }

        if(StrUtil.isNotEmpty(eventPublisher.binding().key()) && StrUtil.isEmpty(eventPublisher.binding().exchange().name())) {
            throw new EventConfigException("@EventPublisher binding, exchange name can not be empty");
        }

        if(StrUtil.isNotEmpty(eventPublisher.binding().key()) && StrUtil.isEmpty(eventPublisher.binding().queue().name())) {
            throw new EventConfigException("@EventPublisher binding, queue name can not be empty");
        }
    }

    protected void validation(EventListener eventListener) {
        if (eventListener == null) {
            throw new NullPointerException("@EventListener can not be empty");
        }

        if(StrUtil.isEmpty(eventListener.key()) && eventListener.queuesToDeclare().length <= 0 && eventListener.bindings().length <= 0) {
            throw new EventConfigException("@EventListener key or queuesToDeclare or bindings can not be empty");
        }

        if(StrUtil.isNotEmpty(eventListener.key()) && eventListener.queuesToDeclare().length > 0 && eventListener.bindings().length > 0) {
            throw new EventConfigException("@EventListener can have only one of 'key', 'queuesToDeclare', or 'bindings'");
        }

        if(eventListener.queuesToDeclare().length > 1) {
            List<EventQueue> eventQueueList = Arrays.asList(eventListener.queuesToDeclare());
            eventQueueList.stream().forEach(source->{
                if(StrUtil.isEmpty(source.name())){
                    throw new EventConfigException("@EventListener queuesToDeclare name can not be empty");
                }
            });
        }

        if(eventListener.bindings().length > 1) {
            List<EventBinding> eventBindingList = Arrays.asList(eventListener.bindings());
            eventBindingList.stream().forEach(source->{
                if(StrUtil.isEmpty(source.key())){
                    throw new EventConfigException("@EventListener bindings key can not be empty");
                }
            });
        }

    }

}
